package com.atguigu.gmall.index.service.impl;

import com.atguigu.gmall.common.bean.ResponseVo;
import com.atguigu.gmall.index.aspect.GmallCache;
import com.atguigu.gmall.index.feign.PmsClient;
import com.atguigu.gmall.index.service.IndexService;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import org.apache.commons.lang3.StringUtils;
import org.redisson.RedissonMultiLock;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Service
public class IndexServiceImpl implements IndexService {
    @Autowired
    PmsClient pmsClient;
    @Autowired
    RedisTemplate redisTemplate;
    @Override
    public List<CategoryEntity> queryLevelOneCategories() {
        ResponseVo<List<CategoryEntity>> responseVo = pmsClient.parent("0");
        return responseVo.getData();
    }


    @GmallCache(key = "idx:cache:cates" ,lockName = "cates:lock",timeout = 30*60 , random = 10*60)
    @Override
    public List<CategoryEntity> levelTwoAndSubsCates(String cid) {
        ResponseVo<List<CategoryEntity>> responseVo;
        responseVo = pmsClient.lv2AndSubsCates(cid);
        return responseVo.getData();
    }








    //管理缓存
    //需要配置缓存的key: 使用value和key的值拼接，key中可以获取业务方法的形参值
    //下面的案例拼接的key为： idx:cache::cates:cid的值
//    @Cacheable(value = "idx:cache" , key = "'cates:'+#cid")
//    @CacheEvict() 缓存失效  删除更新方法上可以指定那个缓存失效
    // 自定义注解需要管理的参数：缓存的key、锁的名称(击穿)、缓存过期时间、随机过期时间(雪崩)
//    @Override
//    public List<CategoryEntity> levelTwoAndSubsCates(String cid) {
//        //使用缓存管理
//        //1、从缓存中查询cid的二级三级分类集合  如果有直接返回
//        String key = "idx:cache:cates:"+cid;
//        Object obj = redisTemplate.opsForValue().get(key);
//        if(obj!=null){
//            return (List<CategoryEntity>) obj;
//        }
//        //缓存中没有数据，加锁控制只让一个线程查询数据库的数据
//        RLock lock = redissonClient.getLock("cates:lock");
//        lock.lock();//加锁
//        // 解决并发多个等待获取锁查询数据库数据的线程每个都查询数据库：再次查询缓存 如果有缓存直接返回
//        obj = redisTemplate.opsForValue().get(key);
//        if(obj!=null){
//            return (List<CategoryEntity>) obj;
//        }
//        //2、如果缓存没有 再远程查询二三级分类集合 存到缓存中
//        ResponseVo<List<CategoryEntity>> responseVo;
//        try {
//            responseVo = pmsClient.lv2AndSubsCates(cid);
//            if(CollectionUtils.isEmpty(responseVo.getData())){
//                redisTemplate.opsForValue()
//                        .set(key,responseVo.getData() ,
//                                new Random().nextInt(500), TimeUnit.SECONDS);
//            }else{
//                redisTemplate.opsForValue()
//                        .set(key,responseVo.getData() ,
//                                1800+new Random().nextInt(200), TimeUnit.SECONDS);
//            }
//        } finally {
//            //释放锁
//            lock.unlock();
//        }
//
//        return responseVo.getData();
//    }

    public void check(String lockKey,String uuid ){
        Boolean lock = tryLock(lockKey,uuid,30L);
        if(lock){

            System.out.println("环境检查.....");
            releaseLock(lockKey,uuid);
        }else{
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            check(lockKey,uuid);
        }

    }
//    @Override
//    public void testLock() throws InterruptedException {
//        String uuid = UUID.randomUUID().toString();
//        Boolean flag = tryLock("lock" , uuid , 30L);
//        if(flag){
////            Thread.sleep(100000);
//            check("lock" ,uuid);
//            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
//            //++
//            num++;
//            redisTemplate.opsForValue().set("num",num);
////            Object lock = redisTemplate.opsForValue().get("lock");
////            if(lock!=null && StringUtils.equals(lock.toString(),uuid)){
////                redisTemplate.delete("lock");
////            }
//            //LUA解决原子性问题：
//            /*
//                使用lua脚本实现上面的业务
//                1、判断有没有lock锁
//                    如果没有 不删除：锁过期 没有其他线程使用锁
//                2、如果有
//                    获取lock的value和uuid进行比较
//                    如果不一样 不删除：不是自己的锁
//                3、如果一样
//                    删除自己的锁
//             */
//            releaseLock("lock",uuid);
//        }else{//获取锁失败：再次尝试获取锁
//            Thread.sleep(50);
//            testLock();//如果不让获取锁失败的线程等待一会再次获取锁可能导致栈溢出异常
//
//        }
//
//
//    }

    @Autowired
    RedissonClient redissonClient;
    @Override
    public void testLock() throws InterruptedException {
        //加锁
//        redissonClient.getLock("") 获取一个可重入锁
//        RLock fairLock = redissonClient.getFairLock("");//获取一个公平锁
//        RLock rLock = redissonClient.getLock("lock");
//        rLock.lock(); 阻塞等待获取锁
//        rLock.lock(10 , TimeUnit.SECONDS); 阻塞等待获取锁   获取到锁之后设置锁的过期时间为10秒
//        rLock.tryLock() 获取锁 成功返回true 失败返回false
//        rLock.tryLock(10，TimeUnit.SECONDS) 阻塞10秒等待获取锁
//        rLock.tryLock(10,5,TimeUnit.SECONDS) 阻塞等待10秒获取锁，获取到锁之后设置过期时间为5秒

        RLock lock1 = redissonClient.getLock("lock1");
        RLock lock2 = redissonClient.getLock("lock2");
        RLock lock3 = redissonClient.getLock("lock3");
        //将上面的三个锁组成一个联锁
        RedissonMultiLock multiLock = new RedissonMultiLock(lock1, lock2, lock3);
        //遵循redlock算法实现的分布式锁：
        // 1、redis多master节点启动：获取锁的节点超过一半获取锁成功： 释放锁所有节点都会执行释放
        // 2、锁有过期时间  3、资源独享
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            multiLock.lock();//执行耗时较短的业务时 使用阻塞等待获取锁  默认的锁过期时间是3秒
            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
            //++
            num++;
            redisTemplate.opsForValue().set("num",num);
        } finally {
            //尽量在finally中释放锁
            multiLock.unlock();
        }

    }

    public void releaseLock(String lock , String uuid) {
//        String script = "if redis.call('exists' , KEYS[1])==0 " +
//                " then return 0 " +//锁空闲
//                " elseif redis.call('get' , KEYS[1])== ARGV[1] " +//是自己的锁
//                " then return redis.call('del' , KEYS[1]) " +
//                " else return 0 end";//锁不是自己的
//        redisTemplate.execute(new DefaultRedisScript(script,Boolean.class),
//                Arrays.asList("lock") , uuid);
        String script = "if redis.call('hexists' ,KEYS[1] , KEYS[2])==0 " +
                " then " +
                "   return 0 " +
                " elseif tonumber(redis.call('hget' , KEYS[1] , KEYS[2])) == 1 " + //获取锁使用的次数 判断是否是1次
                "  then " +//释放锁
                "    redis.call('del' ,KEYS[1]) return 1 " +
                " else " + //重入锁重入的次数-1
                "    redis.call('hincrby' , KEYS[1] , KEYS[2], -1) return 2 end";
        System.out.println(script);
        redisTemplate.execute(new DefaultRedisScript(script,Long.class) , Arrays.asList(lock,uuid));
    }

    public Boolean tryLock(String lock , String uuid , Long expireTime) {
//        return redisTemplate.opsForValue()
//                    .setIfAbsent("lock", uuid,30, TimeUnit.SECONDS);
        String script = "if redis.call('exists' , KEYS[1])==0 " +
                " then " +//锁不存在 直接获取锁
                "   redis.call('hset' , KEYS[1] , KEYS[2] , 1) " +
                "   redis.call('expire' , KEYS[1] ,ARGV[1]) " +
                "   return 1 " +
                " elseif redis.call('hexists' , KEYS[1] , KEYS[2])==1 " +
                " then " +//锁存在，而且是自己的
                "   redis.call('hincrby' , KEYS[1] , KEYS[2] , 1) return 2 " +
                " else return 0  end ";//锁不是自己的

        Long flag = (Long) redisTemplate.execute(new DefaultRedisScript(script,Long.class),
            Arrays.asList(lock , uuid),expireTime);
         //如果第一次获取锁成功：创建看门狗任务为锁自动续期
        if(flag==1){
            renewKey(expireTime,lock,uuid);
        }
        return flag!=0;
    }
    ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
    //看门狗：自动续期方法
    public void renewKey(Long expireTime,String lock , String uuid) {//expireTime 是lock锁的过期时间  单位秒
        pool.schedule(()->{
            //给分布式锁的键设置过期时间: 给调用此方法的任务的锁续期
            //如果 key 和uuid的 锁一直存在 一直续期 ，如果不存在 任务停止
            String script = "if redis.call('hexists' , KEYS[1] , KEYS[2])==1 " +
                    " then redis.call('expire' ,KEYS[1] ,ARGV[1]) return 1 " + //续期成功
                    " else return 0 end"; //续期失败
            while((Boolean)redisTemplate.execute(new DefaultRedisScript(script,Boolean.class) ,
                    Arrays.asList(lock,uuid) ,  expireTime)){
                try {
                    Thread.sleep(expireTime*2000/3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            };
        },expireTime*2000/3 ,TimeUnit.MILLISECONDS);//初始化时 任务只会执行一次

    }



//    @Override
//    public void testLock() {
//        String uuid = UUID.randomUUID().toString();
//        //获取锁setIfAbsent: 对应的是setnx指令   设置成功返回true
////        Boolean flag = redisTemplate.opsForValue().setIfAbsent("lock", "hehe");
//        Boolean flag = redisTemplate.opsForValue()
//                .setIfAbsent("lock", uuid,2,TimeUnit.SECONDS);
//        if(flag){
//            //获取num的值
//            int num = Integer.parseInt(redisTemplate.opsForValue().get("num").toString());
//            //++
//            num++;
////            int i = 1/0;
//            //设置到redis中
//            redisTemplate.opsForValue().set("num",num);
//            //释放锁：为了保证锁正常释放  在finally中释放锁   可能死锁
////            redisTemplate.delete("lock");
//            Object lock = redisTemplate.opsForValue().get("lock");
//            if(lock!=null && StringUtils.equals(lock.toString(),uuid)){
//                //删除自己的锁
//                redisTemplate.delete("lock");
//            }
//        }else{//获取锁失败：再次尝试获取锁
//            try {
//                Thread.sleep(50);
//                testLock();//如果不让获取锁失败的线程等待一会再次获取锁可能导致栈溢出异常
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//
//        }
//
//
//    }
}
